package com.jxin.mall.core.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.jxin.mall.core.exception.ProtoStuffException;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

/**
 * ProtoStuff 序列化器的工具类
 * @author  蔡佳新
 * @version 1.0
 * @since   jdk 1.8
 */
@Slf4j
public class ProtoStuffUtil {

    /**LinkedBuffer 默认长度*/
    private static final Integer LINKED_BUFFER_DEF_SIZE= 1024 * 1024;

    /**
     * 序列化的对象
     * @param obj 要序列化的对象
     * @param <T> 对象类型
     * @throws ProtoStuffException 序列化失败时
     * @return 序列化后的字节数组
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public static <T> byte[] serialize(@NotNull T obj) throws ProtoStuffException{
        Assert.notNull(obj, ProtoStuffUtilMsg.NON_NULL_OBJ_REQUIRED);
        final LinkedBuffer buffer = LinkedBuffer.allocate(LINKED_BUFFER_DEF_SIZE);
        try {
            return ProtostuffIOUtil.toByteArray(obj,
                                                (Schema<T>) RuntimeSchema.getSchema(obj.getClass()),
                                                buffer);
        } catch (Exception e) {
            throw new ProtoStuffException(e, ProtoStuffUtilMsg.SERIALIZE_OBJ_ERR_TEMP, obj);
        } finally {
            buffer.clear();
        }

    }

    /**
     * 反序列化对象
     * @param objByteArr 对象的字节数组
     * @param clazz      对象的字节码类型
     * @param <T>        对象类型
     * @throws ProtoStuffException  反序列化失败时
     * @return 对象实例
     * @author 蔡佳新
     */
    @Nullable
    public static <T> T deserialize(byte[] objByteArr, @NotNull Class<T> clazz) throws ProtoStuffException{
        if(ArrayUtils.isEmpty(objByteArr)){
            return null;
        }
        Assert.notNull(clazz, ProtoStuffUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        final T result;
        try {
            result = clazz.newInstance();
            ProtostuffIOUtil.mergeFrom(objByteArr, result, RuntimeSchema.getSchema(clazz));
        } catch (InstantiationException | IllegalAccessException e) {
            throw new ProtoStuffException(e, ProtoStuffUtilMsg.REVERSE_SERIALIZE_OBJ_ERR_TEMP, clazz);
        }
        return result;
    }

    /**
     * 序列化对象列表
     * @param objList 要序列化的对象列表
     * @param <T>     对象类型
     * @throws ProtoStuffException  序列化失败时
     * @return 序列化后的字节数组
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public static <T> byte[] serializeList(@NotEmpty List<T> objList) {
        Assert.notEmpty(objList, ProtoStuffUtilMsg.NON_EMPTY_OBJ_LIST_REQUIRED);
        final LinkedBuffer buffer = LinkedBuffer.allocate(LINKED_BUFFER_DEF_SIZE);
        byte[] result;
        try(final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            ProtostuffIOUtil.writeListTo(bos,
                                         objList,
                                         (Schema<T>) RuntimeSchema.getSchema(CollUtil.getFirst(objList).getClass()),
                                         buffer);
            result = bos.toByteArray();
        } catch (Exception e) {
            throw new ProtoStuffException(e, ProtoStuffUtilMsg.MULTI_SERIALIZE_OBJ_ERR_TEMP, JSONUtil.toJsonStr(objList));
        }finally {
            buffer.clear();
        }
        return result;
    }

    /**
     * 反序列化对象列表
     * @param  objByteArr 对象字节数组
     * @param  clazz      字节码类型
     * @param  <T>        对象类型
     * @throws ProtoStuffException  反序列化失败时
     * @return 对象列表
     * @author 蔡佳新
     */
    public static <T> List<T> deserializeList(@NotEmpty byte[] objByteArr, @NotNull Class<T> clazz) {
        Assert.notNull(objByteArr, ProtoStuffUtilMsg.NON_EMPTY_OBJ_BYTE_ARR_REQUIRED);
        Assert.notNull(clazz, ProtoStuffUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        List<T> result;
        try {
            result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(objByteArr), schema);
        } catch (IOException e) {
            throw new ProtoStuffException(e, ProtoStuffUtilMsg.MULTI_REVERSE_SERIALIZE_OBJ_ERR_TEMP, clazz);
        }
        return result;
    }
    //---------------------------------------------------logMsg---------------------------------------------------------
    /**
     * ProtoStuff工具类 的内部消息类
     * @author  蔡佳新
     * @version 1.0
     * @since   jdk 1.8
     */
    private interface ProtoStuffUtilMsg{
        /**对象为null的异常信息*/
        String NON_NULL_OBJ_REQUIRED = "non null obj required";
        /**对象字节数组为空的异常信息*/
        String NON_EMPTY_OBJ_BYTE_ARR_REQUIRED = "non empty objByteArr required";
        /**对象字节码类型为null的异常信息*/
        String NON_NULL_CLAZZ_REQUIRED = "non null clazz required";
        /**对象集合为空的异常信息*/
        String NON_EMPTY_OBJ_LIST_REQUIRED = "non empty objList required";
        /**序列化统一前缀*/
        String TEMP_PREFIX = "[ProtoStuff序列化],";
        /**单对象序列化失败消息模板*/
        String SERIALIZE_OBJ_ERR_TEMP = TEMP_PREFIX + "单对象序列化失败, obj:{}";
        /**单对象反序列化失败消息模板*/
        String REVERSE_SERIALIZE_OBJ_ERR_TEMP = TEMP_PREFIX + "单对象反序列化失败, clazz:{}";
        /**多对象序列化失败消息模板*/
        String MULTI_SERIALIZE_OBJ_ERR_TEMP = TEMP_PREFIX + "多对象序列化失败, obj:{}";
        /**多对象反序列化失败消息模板*/
        String MULTI_REVERSE_SERIALIZE_OBJ_ERR_TEMP = TEMP_PREFIX + "多对象反序列化失败, clazz:{}";
    }
}
